Оглавление

  • 1  Постановка задачи
    • 1.1  Техническое задание
  • 2  Обзор данных
  • 3  Предобработка
    • 3.1  Календарь маркетинговых событий
    • 3.2  Действия новых пользователей
    • 3.3  Новые пользователи
    • 3.4  Участники теста
  • 4  Оценка корректности проведения теста
    • 4.1  Соответствие данных требованиям ТЗ
      • 4.1.1  Запуск и остановка теста
      • 4.1.2  Запуск и остановка набора пользователей
      • 4.1.3  Аудитория теста
    • 4.2  Время проведения теста
    • 4.3  Аудитория теста
    • 4.4  Выводы
  • 5  Исследовательский анализ
    • 5.1  Распределение количества событий на пользователя
    • 5.2  Распределение количества событий по дням
    • 5.3  Изменение конверсии на различных этапах
      • 5.3.1  Контрольная группа (А)
      • 5.3.2  Тестируемая группа (B)
    • 5.4  Особенности данных при запуске A/B тестирования
  • 6  Оценка результатов A/B тестирования
    • 6.1  Обзор результатов тестирования
    • 6.2  Проверка статистической разницы долей
  • 7  Выводы
    • 7.1  Рекомендации

Постановка задачи¶

Цель исследования - провести оценку результатов A/B-теста.

  1. Необходимо оценить корректность проведения теста путем поверки пересечения аудитории участников теста
  2. Проанализировать результаты теста

Техническое задание¶

  • Название теста: recommender_system_test;
  • группы: А — контрольная, B — новая платёжная воронка;
  • дата запуска: 2020-12-07;
  • дата остановки набора новых пользователей: 2020-12-21;
  • дата остановки: 2021-01-04;
  • аудитория: 15% новых пользователей из региона EU;
  • назначение теста: тестирование изменений, связанных с внедрением улучшенной рекомендательной системы;
  • ожидаемое количество участников теста: 6000;
  • ожидаемый эффект: за 14 дней с момента регистрации пользователи покажут улучшение каждой метрики не менее, чем на 10%:
    • конверсии в просмотр карточек товаров — событие product_page,
    • просмотры корзины — product_cart,
    • покупки — purchase.

Обзор данных¶

In [1]:
import pandas as pd
import numpy as np
import math as mth
from scipy import stats as st
import seaborn as sns
import matplotlib.pyplot as plt
from plotly import graph_objects as go
from datetime import datetime

import warnings
warnings.filterwarnings('ignore')
In [2]:
sns.set_style("darkgrid")
In [3]:
try:
    marketing_events = pd.read_csv('Datasets/ab_project_marketing_events.csv')
except:
    marketing_events = pd.read_csv('https://code.s3.yandex.net/datasets/ab_project_marketing_events.csv')

try:
    ab_events = pd.read_csv('Datasets/final_ab_events.csv')
except:
    ab_events = pd.read_csv('https://code.s3.yandex.net/datasets/final_ab_events.csv')
    
try:
    new_users = pd.read_csv('Datasets/final_ab_new_users.csv')
except:
    new_users = pd.read_csv('https://code.s3.yandex.net/datasets/final_ab_new_users.csv')
    
try:
    participants = pd.read_csv('Datasets/final_ab_participants.csv')
except:
    participants = pd.read_csv('https://code.s3.yandex.net/datasets/final_ab_participants.csv')
In [4]:
marketing_events.head()
Out[4]:
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
1 St. Valentine's Day Giveaway EU, CIS, APAC, N.America 2020-02-14 2020-02-16
2 St. Patric's Day Promo EU, N.America 2020-03-17 2020-03-19
3 Easter Promo EU, CIS, APAC, N.America 2020-04-12 2020-04-19
4 4th of July Promo N.America 2020-07-04 2020-07-11

ab_project_marketing_events.csv — календарь маркетинговых событий на 2020 год.

  • name — название маркетингового события;
  • regions — регионы, в которых будет проводиться рекламная кампания;
  • start_dt — дата начала кампании;
  • finish_dt — дата завершения кампании.
In [5]:
ab_events.head()
Out[5]:
user_id event_dt event_name details
0 E1BDDCE0DAFA2679 2020-12-07 20:22:03 purchase 99.99
1 7B6452F081F49504 2020-12-07 09:22:53 purchase 9.99
2 9CD9F34546DF254C 2020-12-07 12:59:29 purchase 4.99
3 96F27A054B191457 2020-12-07 04:02:40 purchase 4.99
4 1FD7660FDF94CA1F 2020-12-07 10:15:09 purchase 4.99

final_ab_events.csv — действия новых пользователей в период с 7 декабря 2020 по 4 января 2021 года.

  • user_id — идентификатор пользователя;
  • event_dt — дата и время события;
  • event_name — тип события;
  • details — дополнительные данные о событии. Например, для покупок, purchase, в этом поле хранится стоимость покупки в долларах.
In [6]:
new_users.head()
Out[6]:
user_id first_date region device
0 D72A72121175D8BE 2020-12-07 EU PC
1 F1C668619DFE6E65 2020-12-07 N.America Android
2 2E1BF1D4C37EA01F 2020-12-07 EU PC
3 50734A22C0C63768 2020-12-07 EU iPhone
4 E1BDDCE0DAFA2679 2020-12-07 N.America iPhone

final_ab_new_users.csv — пользователи, зарегистрировавшиеся с 7 по 21 декабря 2020 года.

  • user_id — идентификатор пользователя;
  • first_date — дата регистрации;
  • region — регион пользователя;
  • device — устройство, с которого происходила регистрация.
In [7]:
participants.head()
Out[7]:
user_id group ab_test
0 D1ABA3E2887B6A73 A recommender_system_test
1 A7A3664BD6242119 A recommender_system_test
2 DABC14FDDFADD29E A recommender_system_test
3 04988C5DF189632E A recommender_system_test
4 482F14783456D21B B recommender_system_test

final_ab_participants.csv — таблица участников тестов.

  • user_id — идентификатор пользователя;
  • ab_test — название теста;
  • group — группа пользователя.

Предобработка¶

Перед началом исследовательского анализа необходимо проверить данные на наличие пропусков и дубликатов, а также на корректность форматов данных.

In [8]:
def date (df, col):
    for c in col:
        df[c] = pd.to_datetime(df[c])

Календарь маркетинговых событий¶

In [9]:
marketing_events.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 4 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   name       14 non-null     object
 1   regions    14 non-null     object
 2   start_dt   14 non-null     object
 3   finish_dt  14 non-null     object
dtypes: object(4)
memory usage: 576.0+ bytes
In [10]:
date(marketing_events, ['start_dt', 'finish_dt'])
In [11]:
marketing_events.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 4 columns):
 #   Column     Non-Null Count  Dtype         
---  ------     --------------  -----         
 0   name       14 non-null     object        
 1   regions    14 non-null     object        
 2   start_dt   14 non-null     datetime64[ns]
 3   finish_dt  14 non-null     datetime64[ns]
dtypes: datetime64[ns](2), object(2)
memory usage: 576.0+ bytes
In [12]:
marketing_events
Out[12]:
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
1 St. Valentine's Day Giveaway EU, CIS, APAC, N.America 2020-02-14 2020-02-16
2 St. Patric's Day Promo EU, N.America 2020-03-17 2020-03-19
3 Easter Promo EU, CIS, APAC, N.America 2020-04-12 2020-04-19
4 4th of July Promo N.America 2020-07-04 2020-07-11
5 Black Friday Ads Campaign EU, CIS, APAC, N.America 2020-11-26 2020-12-01
6 Chinese New Year Promo APAC 2020-01-25 2020-02-07
7 Labor day (May 1st) Ads Campaign EU, CIS, APAC 2020-05-01 2020-05-03
8 International Women's Day Promo EU, CIS, APAC 2020-03-08 2020-03-10
9 Victory Day CIS (May 9th) Event CIS 2020-05-09 2020-05-11
10 CIS New Year Gift Lottery CIS 2020-12-30 2021-01-07
11 Dragon Boat Festival Giveaway APAC 2020-06-25 2020-07-01
12 Single's Day Gift Promo APAC 2020-11-11 2020-11-12
13 Chinese Moon Festival APAC 2020-10-01 2020-10-07

Пропущенные значения: Отсутствуют
Дубликаты: Отсутствуют
Форматы данных: Даты приведены к формату datetime

Действия новых пользователей¶

In [13]:
ab_events.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 440317 entries, 0 to 440316
Data columns (total 4 columns):
 #   Column      Non-Null Count   Dtype  
---  ------      --------------   -----  
 0   user_id     440317 non-null  object 
 1   event_dt    440317 non-null  object 
 2   event_name  440317 non-null  object 
 3   details     62740 non-null   float64
dtypes: float64(1), object(3)
memory usage: 13.4+ MB
In [14]:
date(ab_events, ['event_dt'])
In [15]:
ab_events.head()
Out[15]:
user_id event_dt event_name details
0 E1BDDCE0DAFA2679 2020-12-07 20:22:03 purchase 99.99
1 7B6452F081F49504 2020-12-07 09:22:53 purchase 9.99
2 9CD9F34546DF254C 2020-12-07 12:59:29 purchase 4.99
3 96F27A054B191457 2020-12-07 04:02:40 purchase 4.99
4 1FD7660FDF94CA1F 2020-12-07 10:15:09 purchase 4.99
In [16]:
ab_events[ab_events['event_name'] == "purchase"].info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 62740 entries, 0 to 62739
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   user_id     62740 non-null  object        
 1   event_dt    62740 non-null  datetime64[ns]
 2   event_name  62740 non-null  object        
 3   details     62740 non-null  float64       
dtypes: datetime64[ns](1), float64(1), object(2)
memory usage: 2.4+ MB
In [17]:
ab_events['event_name'].unique()
Out[17]:
array(['purchase', 'product_cart', 'product_page', 'login'], dtype=object)

Пропущенные значения: В поле details, где event_name != purchase, заполнение не требуется
Дубликаты: Отсутствуют
Форматы данных: Даты приведены к формату datetime

Новые пользователи¶

In [18]:
new_users.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 61733 entries, 0 to 61732
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   user_id     61733 non-null  object
 1   first_date  61733 non-null  object
 2   region      61733 non-null  object
 3   device      61733 non-null  object
dtypes: object(4)
memory usage: 1.9+ MB
In [19]:
date(new_users, ['first_date'])
In [20]:
new_users.head()
Out[20]:
user_id first_date region device
0 D72A72121175D8BE 2020-12-07 EU PC
1 F1C668619DFE6E65 2020-12-07 N.America Android
2 2E1BF1D4C37EA01F 2020-12-07 EU PC
3 50734A22C0C63768 2020-12-07 EU iPhone
4 E1BDDCE0DAFA2679 2020-12-07 N.America iPhone
In [21]:
sum(new_users['user_id'].duplicated())
Out[21]:
0
In [22]:
new_users['region'].unique()
Out[22]:
array(['EU', 'N.America', 'APAC', 'CIS'], dtype=object)
In [23]:
new_users['device'].unique()
Out[23]:
array(['PC', 'Android', 'iPhone', 'Mac'], dtype=object)

Пропущенные значения: Отсутствуют
Дубликаты: Отсутствуют
Форматы данных: Даты приведены к формату datetime

Участники теста¶

In [24]:
participants.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18268 entries, 0 to 18267
Data columns (total 3 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   user_id  18268 non-null  object
 1   group    18268 non-null  object
 2   ab_test  18268 non-null  object
dtypes: object(3)
memory usage: 428.3+ KB
In [25]:
participants.head()
Out[25]:
user_id group ab_test
0 D1ABA3E2887B6A73 A recommender_system_test
1 A7A3664BD6242119 A recommender_system_test
2 DABC14FDDFADD29E A recommender_system_test
3 04988C5DF189632E A recommender_system_test
4 482F14783456D21B B recommender_system_test
In [26]:
sum(participants.duplicated())
Out[26]:
0
In [27]:
sum(participants['user_id'].duplicated())
Out[27]:
1602
In [28]:
participants['group'].unique()
Out[28]:
array(['A', 'B'], dtype=object)
In [29]:
participants['ab_test'].unique()
Out[29]:
array(['recommender_system_test', 'interface_eu_test'], dtype=object)

Пропущенные значения: Отсутствуют
Дубликаты: Некоторые значения user_id повторяются, следовательно некоторые пользователи участвуют одновременно в нескольких тестах или нескольких группах
Форматы данных: Корректны

Оценка корректности проведения теста¶

Соответствие данных требованиям ТЗ¶

Запуск и остановка теста¶

In [30]:
participants = participants.merge(new_users, on='user_id')
rec_participants = participants.query('ab_test == "recommender_system_test"')
In [31]:
ab_events = ab_events.query('user_id in @rec_participants["user_id"]')
In [32]:
ab_events['event_dt'].min()
Out[32]:
Timestamp('2020-12-07 00:05:57')
In [33]:
ab_events['event_dt'].max()
Out[33]:
Timestamp('2020-12-30 12:42:57')

Дата запуска соответствует заявленной,
Дата остановки на 5 дней раньше запланированной
Также возможно что пользователи не совершали действий в период с 31.12.2020 по 04.01.2021

Запуск и остановка набора пользователей¶

In [34]:
new_users.query('user_id in @rec_participants["user_id"]')['first_date'].min()
Out[34]:
Timestamp('2020-12-07 00:00:00')
In [35]:
new_users.query('user_id in @rec_participants["user_id"]')['first_date'].max()
Out[35]:
Timestamp('2020-12-21 00:00:00')

Дата запуска и остановки набора новых пользователей соответствуют заявленным.

In [36]:
new_users = new_users.query('first_date <= "2020-12-22"')
In [37]:
new_users['first_date'].max()
Out[37]:
Timestamp('2020-12-22 00:00:00')

Пользователи отфильтрованы по датам набора в соответствии с ТЗ.

Аудитория теста¶

In [38]:
rec_participants['region'].unique()
Out[38]:
array(['EU', 'N.America', 'CIS', 'APAC'], dtype=object)
In [39]:
rec_participants = rec_participants.query('region == "EU"')

Некоторые участники теста не принадлежат к региону EU, что не соответствует ТЗ.
Данные отфильтрованы

In [40]:
len(rec_participants)
Out[40]:
6351
In [41]:
new_eu_users = new_users.query('region == "EU"')

new_relevant_eu_users = new_eu_users.query('first_date >= "2020-12-07"')
new_relevant_eu_users = new_relevant_eu_users.query('first_date <= "2020-12-21"')

len(new_eu_users)
Out[41]:
44632
In [42]:
round(len(rec_participants) / len(new_relevant_eu_users), 3)
Out[42]:
0.15

Количество участников теста соответствует ТЗ:
6351 пользователь
15% от новых пользователей региона EU

Время проведения теста¶

In [43]:
ab_events['event_dt'].min()
Out[43]:
Timestamp('2020-12-07 00:05:57')
In [44]:
ab_events['event_dt'].max()
Out[44]:
Timestamp('2020-12-30 12:42:57')
In [45]:
marketing_events
Out[45]:
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
1 St. Valentine's Day Giveaway EU, CIS, APAC, N.America 2020-02-14 2020-02-16
2 St. Patric's Day Promo EU, N.America 2020-03-17 2020-03-19
3 Easter Promo EU, CIS, APAC, N.America 2020-04-12 2020-04-19
4 4th of July Promo N.America 2020-07-04 2020-07-11
5 Black Friday Ads Campaign EU, CIS, APAC, N.America 2020-11-26 2020-12-01
6 Chinese New Year Promo APAC 2020-01-25 2020-02-07
7 Labor day (May 1st) Ads Campaign EU, CIS, APAC 2020-05-01 2020-05-03
8 International Women's Day Promo EU, CIS, APAC 2020-03-08 2020-03-10
9 Victory Day CIS (May 9th) Event CIS 2020-05-09 2020-05-11
10 CIS New Year Gift Lottery CIS 2020-12-30 2021-01-07
11 Dragon Boat Festival Giveaway APAC 2020-06-25 2020-07-01
12 Single's Day Gift Promo APAC 2020-11-11 2020-11-12
13 Chinese Moon Festival APAC 2020-10-01 2020-10-07

Даты проведения эксперимента частично пересекаются с маркетинговым событием Christmas & New Year Promo.
Необходимо будет учитывать это при изучении абсолютных значений, поскольку набор статистической значимости эксперимента может распределяться неравномерно.

Аудитория теста¶

In [46]:
participants_pivot = participants.groupby('user_id').agg('nunique')[['group', 'ab_test']]

participants_pivot
Out[46]:
group ab_test
user_id
0002CE61FF2C4011 1 1
000ABE35EE11412F 1 1
001064FEAAB631A1 1 2
0010A1C096941592 1 1
001C05E87D336C59 1 1
... ... ...
FFE858A7845F005E 1 1
FFED90241D04503F 1 2
FFEFC0E55C1CCD4F 1 1
FFF28D02B1EACBE1 2 2
FFF58BC33966EB51 1 1

16666 rows × 2 columns

Как было обнаружено ранее, часть пользователей принимают участие одновременно в нескольких тестах или находятся одновременно в обеих исследуемых группах.

In [47]:
participants_pivot_filtered = participants_pivot.query('group == 1')
participants_pivot_filtered = participants_pivot_filtered.query('ab_test == 1')
In [48]:
participants_filtered = rec_participants.query('user_id in @participants_pivot_filtered.index')

Также, поскольку регистрация событий пользователей была завершена ранее запланированной даты, необходимо исключить пользователей, лайфтайм которых оказался меньше "горизонта событий" (14 дней) чтобы убедиться в том, что у пользователей было достаточно времени на совершение всех действий.

In [49]:
participants_filtered = participants_filtered.query('first_date <= "2020-12-16"')
In [50]:
participants_filtered['user_id'].nunique()
Out[50]:
3035

При этом количество пользователей оказалось на 50% меньше заявленного.

In [51]:
participants_filtered.query('group == "A"')['user_id'].nunique()
Out[51]:
1743
In [52]:
participants_filtered.query('group == "B"')['user_id'].nunique()
Out[52]:
1292

Распределение пользователей по группам неравномерное.
Следовательно, при дальнейшем анализе необходимо оперировать относительными показателями.

In [53]:
participants_filtered.groupby('device')['user_id'].agg('nunique').sort_values().index
Out[53]:
Index(['Mac', 'iPhone', 'PC', 'Android'], dtype='object', name='device')
In [54]:
plt.figure(figsize=(10, 5))

sns.countplot(data = participants_filtered, 
              x = 'device', 
              hue = 'group', 
              order = participants_filtered.groupby('device')['user_id'].agg('nunique').sort_values().index, 
              
             )

plt.title('Распределение пользователей по устройствам')
plt.ylabel('Количество пользователей')
plt.xlabel('Используемое устройство');

Распределение пользователей по типу используемых устройств идентчно.

Выводы¶

При наборе пользователей и проведении теста замечены некоторые неточности:

  • Некоторые пользователи находятся в обеих группах или участвуют в нескольких тестах
  • Некоторые пользователи теста не относятся к исследуемому региону
  • Дата остановки теста значительно раньше запланированной, что существенно снизило как количество зарегистрированных событий, так и число уникальных пользователей

Исследовательский анализ¶

In [55]:
ab_events_filtered = ab_events.query('user_id in @participants_filtered["user_id"]')
ab_events_filtered = ab_events_filtered.merge(participants, on='user_id')

Распределение количества событий на пользователя¶

In [56]:
user_events_count = ab_events_filtered.pivot_table(index = ['user_id', 'group'], 
                                                   values = 'event_dt',
                                                   aggfunc = 'count').reset_index()
In [57]:
plt.figure(figsize=(10, 5))

sns.countplot(data = user_events_count, 
              x = 'event_dt', 
              hue='group'
             )

plt.title('Распределение количества событий, совершаемых пользователем')
plt.ylabel('Количество пользователей')
plt.xlabel('Количество событий');
In [58]:
round(user_events_count.mean(), 2)
Out[58]:
event_dt    6.89
dtype: float64
In [59]:
round(user_events_count.query('group == "A"').mean(), 2)
Out[59]:
event_dt    7.24
dtype: float64
In [60]:
round(user_events_count.query('group == "B"').mean(), 2)
Out[60]:
event_dt    6.15
dtype: float64

Распределение количества событий, совершаемых пользователем близко к нормальному в обеих группах.

В группе B среднее количество событий ниже, чем в контрольной.

Распределение количества событий по дням¶

In [61]:
ab_events_filtered['event_date'] = pd.to_datetime(ab_events_filtered['event_dt']).dt.date
In [62]:
ab_events_filtered
Out[62]:
user_id event_dt event_name details group ab_test first_date region device event_date
0 831887FE7F2D6CBA 2020-12-07 06:50:29 purchase 4.99 A recommender_system_test 2020-12-07 EU Android 2020-12-07
1 831887FE7F2D6CBA 2020-12-09 02:19:17 purchase 99.99 A recommender_system_test 2020-12-07 EU Android 2020-12-09
2 831887FE7F2D6CBA 2020-12-07 06:50:30 product_cart NaN A recommender_system_test 2020-12-07 EU Android 2020-12-07
3 831887FE7F2D6CBA 2020-12-08 10:52:27 product_cart NaN A recommender_system_test 2020-12-07 EU Android 2020-12-08
4 831887FE7F2D6CBA 2020-12-09 02:19:17 product_cart NaN A recommender_system_test 2020-12-07 EU Android 2020-12-09
... ... ... ... ... ... ... ... ... ... ...
9775 60A008C4D5A3B8F2 2020-12-18 22:01:54 login NaN A recommender_system_test 2020-12-16 EU Android 2020-12-18
9776 60A008C4D5A3B8F2 2020-12-21 08:57:33 login NaN A recommender_system_test 2020-12-16 EU Android 2020-12-21
9777 4266741E592070B6 2020-12-16 07:05:00 login NaN A recommender_system_test 2020-12-16 EU iPhone 2020-12-16
9778 4266741E592070B6 2020-12-17 22:35:21 login NaN A recommender_system_test 2020-12-16 EU iPhone 2020-12-17
9779 4266741E592070B6 2020-12-19 06:57:40 login NaN A recommender_system_test 2020-12-16 EU iPhone 2020-12-19

9780 rows × 10 columns

In [63]:
a_date_events_count = ab_events_filtered.query('group == "A"'
                                            ).pivot_table(index = 'event_date',
                                                          values = 'user_id',
                                                          aggfunc = 'count')
a_date_events_count.columns = ['a_events']

b_date_events_count = ab_events_filtered.query('group == "B"'
                                            ).pivot_table(index = 'event_date',
                                                          values = 'user_id',
                                                          aggfunc = 'count')
b_date_events_count.columns = ['b_events']
In [64]:
date_events_count = a_date_events_count.copy()
date_events_count['b_events'] = b_date_events_count

date_events_count
Out[64]:
a_events b_events
event_date
2020-12-07 221 268
2020-12-08 239 169
2020-12-09 282 250
2020-12-10 246 182
2020-12-11 264 115
2020-12-12 273 150
2020-12-13 233 103
2020-12-14 755 193
2020-12-15 780 164
2020-12-16 748 275
2020-12-17 532 134
2020-12-18 384 101
2020-12-19 340 101
2020-12-20 248 83
2020-12-21 240 77
2020-12-22 216 44
2020-12-23 176 82
2020-12-24 160 62
2020-12-25 163 47
2020-12-26 137 45
2020-12-27 142 54
2020-12-28 113 38
2020-12-29 119 30
In [65]:
plt.figure(figsize=(10, 5))

sns.lineplot(y=date_events_count['a_events'], 
                x=date_events_count.index
               )
sns.lineplot(y=date_events_count['b_events'], 
                x=date_events_count.index
               )

plt.title('Количество событий совершаемых пользователями ежедневно')
plt.ylabel('Количество событий')
plt.xlabel('Дата');
In [66]:
round(date_events_count['a_events'].mean(), 2)
Out[66]:
304.83
In [67]:
round(date_events_count['b_events'].mean(), 2)
Out[67]:
120.3

Количество событий, совершаемых пользователями ежедневно имеет значительный разброс. Также заметно резкое повышение спроса, связанного с сезонностью.

При этом повышение количества действий наблюдается только в контрольной группе.
Количество событий в группе B имеет незначительный рост в данный период.

В связи с этим среднее количество событий в группе B заметно ниже, чем в контрольной.

Изменение конверсии на различных этапах¶

Контрольная группа (А)¶

In [68]:
a_whirpool = ab_events_filtered.query('group == "A"').pivot_table(index = 'event_name', 
                                          values = 'user_id', 
                                          aggfunc = 'nunique').sort_values(by='user_id',
                                                                          ascending=False)
a_whirpool.columns = ['a_users_count']
In [69]:
a_whirpool
Out[69]:
a_users_count
event_name
login 969
product_page 623
product_cart 300
purchase 286
In [70]:
fig = go.Figure(go.Funnel(y=a_whirpool.index, 
                          x=a_whirpool['a_users_count']
                         )
               )

fig.update_layout(title="График воронки событий контрольной группы")

fig.show() 

Тестируемая группа (B)¶

In [71]:
b_whirpool = ab_events_filtered.query('group == "B"').pivot_table(index = 'event_name', 
                                          values = 'user_id', 
                                          aggfunc = 'nunique').sort_values(by='user_id',
                                                                          ascending=False)
b_whirpool.columns = ['b_users_count']
In [72]:
b_whirpool
Out[72]:
b_users_count
event_name
login 450
product_page 253
product_cart 125
purchase 124
In [73]:
fig = go.Figure(go.Funnel(y=b_whirpool.index, 
                          x=b_whirpool['b_users_count']
                         )
               )

fig.update_layout(title="График воронки событий исследуемой группы")

fig.show() 
In [74]:
whirpool = a_whirpool.merge(b_whirpool, on='event_name')
In [75]:
whirpool
Out[75]:
a_users_count b_users_count
event_name
login 969 450
product_page 623 253
product_cart 300 125
purchase 286 124
In [76]:
participants_filtered.query('group == "A"')['user_id'].nunique()
Out[76]:
1743
In [77]:
participants_filtered.query('group == "B"')['user_id'].nunique()
Out[77]:
1292

Агрегированные значения совершенных действий пользователями приведены в таблице.

Значения конверсии перехода между этапами для обеих групп близки между собой.
Однако, принимая во внимание исходные размеры выборок заметно, что конверсия пользователей группы B на этапе login заметно н

Особенности данных при запуске A/B тестирования¶

При проведении A/B тестирования необходимо обращать внимание на следующие параметры:

  • Длительность проведения теста не должна быть меньше времени, необходимого пользователю для совершения всех действий в воронке.
  • Сравниваемые группы должны быть равны по косвенным признакам (география пользователей, используемые устройства, возраст, пол и.т.д)
  • Размер выборки должен быть репрезентативен для проведения статистических исследований
  • Распределение данных по времени должно быть равномерным, чтобы статистическая значимость также набиралась постепенно с течением времени
  • Каждый пользователь должен принадлежать только к одной группе

Оценка результатов A/B тестирования¶

Обзор результатов тестирования¶

При подготовке и проведении исследования был допущен ряд неточностей, которые могли повлиять на результаты исследования:

  • Количество пользователей после фильтрации некорректных данных меньше ожидаемого, тем не менее размер наименьшей выборки - 2000 пользователей, а полученные данные имеют нормальное распределение, что позволяет проверять гипотезы статистически.
  • Количество пользователей, совершивших покупку больше, чем пользователей, просмотревших корзину. Это означает, что данные действия не обязательно совершать последовательно. В связи с этим исследовалась конверсия на двух этапах: логин - карточка товара и карточка товара - покупка.

Также стоит обратить внимание на то, что показатель конверсии на первом шаге у группы B оказался ниже, чем у контрольной группы, но выше на втором.

Следует проверить, насколько значимы данные различия.

Проверка статистической разницы долей¶

Формулируются следующие нулевая и альтернативная гипотезы:

Для каждого из этапов:
H0: Конверсия пользователей групп A и B не отличается
H1: Имеется значимое различие конверсии пользователей групп A и B

In [78]:
participants_filtered.query('group == "A"')['user_id'].nunique()
Out[78]:
1743
In [79]:
participants_filtered.query('group == "B"')['user_id'].nunique()
Out[79]:
1292
In [81]:
whirpool['a_users_count'][0]
Out[81]:
969
In [82]:
whirpool['b_users_count'][0] 
Out[82]:
450
In [ ]:
def z_test(event, alpha=0.05):
    
    trials1 = participants_filtered.query('group == "A"')['user_id'].nunique()
    trials2 = participants_filtered.query('group == "B"')['user_id'].nunique()
    
    successes1 = whirpool['a_users_count'][event]
    successes2 = whirpool['b_users_count'][event]  

    
    print(trials1)
    print(trials2)
    print(successes1)
    print(successes2)
In [ ]:
z_test(whirpool.index[0], alpha=0.05 / 4)
In [ ]:
whirpool['a_users_count'] =  round(whirpool['a_users_count'] * 100 / \
                             participants_filtered.query('group == "A"')['user_id'].nunique(), 2)
whirpool['b_users_count'] =  round(whirpool['b_users_count'] * 100 / \
                             participants_filtered.query('group == "B"')['user_id'].nunique(), 2)
whirpool.columns = ['a_total_ratio', 'b_total_ratio']
In [ ]:
whirpool
In [ ]:
plt.figure(figsize=(10, 5))

sns.lineplot(x=whirpool.index, 
             y=whirpool['a_total_ratio'],
             label="A"
            )
sns.lineplot(x=whirpool.index, 
             y=whirpool['b_total_ratio'], 
             label="B"
            )


plt.title('Конверсия пользователей на различных этапах')
plt.ylabel('Общая конверсия, %')
plt.xlabel('Этап');

На каждом из этапов заметно существенное отличие конверсии между группами.
Группа B, с обновленной рекомендательной системой показывает более низкие показатели.

Выводы¶

На проведение данного тестирования оказал влияние ряд факторов, такие как сезонность, пересечение с маркетинговыми событиями. Также при наборе данных были неточности, которые были отфильтровано, что снизило размер выборок.

Тем не менее, результат сравнения конверсии выявил статистически значимую разницу для каждого совершаемого действия.
Пользователи группы B, которые использовали новую рекомендательную систему совершали целевые действия в среднем на 40% реже, что свидетельствует о неудовлетворенности пользователей данной системой.

Рекомендации¶

В контрольной группе наблюдался заметный рост количества пользовательских действий в течение определенного времени.

В то же время для пользователей группы B данный эффект проявился в гораздо меньшей степени.
Помимо этого, на протяжении всего теста пользователи группы B демонстрируют вдвое меньший показатель конверсии.

Результат теста можно считать статистически значимым и при необходимости, повести его повторно после доработки рекомендательной системы, с учетом корректировок распределения пользовательского трафика и длительности проведения теста.